import { Component, cls, type StyledProps } from '@knno/jsx';
import SVGClose from '@mdi/svg/svg/close.svg';
import { Icon } from '../icon/icon';
import { PopList, type PopListSelectHandler } from '../popList/popList';
import { DATA } from '../symbol';
import { tagStyle } from './tag.css';

export type TagValue = string | { value: unknown; text?: string };

function getValue(o: TagValue): unknown {
	if (typeof o === 'string') return o;
	return o.value;
}

function getText(o: TagValue): string {
	if (typeof o === 'string') return o;
	return o.text ?? '';
}

export type TagPopupHandler = (this: Tag, values: TagValue[]) => Promise<TagValue[]>;

export type TagProps = StyledProps<Tag> & {
	prefixIcon?: string;
	selectOnly?: boolean;
	maxTags?: number;
	maxLength?: number;
	placeholder?: string;
	options?: TagValue[];
	value?: TagValue[];
	disabled?: boolean;
	readOnly?: boolean;
	onPopupOpen?: (this: Tag) => void;
	onPopupClose?: (this: Tag) => void;
	onOpenSelection?: TagPopupHandler;
	onClickPrefixIcon?: (this: Tag) => void;
	onChange?: (this: Tag) => void;
};

export class Tag extends Component<TagProps, Tag, HTMLDivElement> {
	private [DATA]: {
		pop?: PopListSelectHandler;
	} = {};

	constructor(props: TagProps) {
		super(props);
		if (this.props.options === undefined) {
			this.props.options = [];
		}
		if (this.props.value === undefined) {
			this.props.value = [];
		}
		this.setDisabled(this.props.disabled ?? false);
		this.setReadOnly(this.props.readOnly ?? false);
		this.setMaxLength(this.props.maxLength ?? null);
		this.setSelectOnly(this.props.selectOnly ?? false);
		this.setPrefixIcon(this.props.prefixIcon ?? null);
		this.setOptions(this.props.options);
		this.setMaxTags(this.props.maxTags ?? null);
		this.setValue(this.props.value);
	}

	protected render(): Node {
		let input: HTMLInputElement;
		let openTimer: number | null = null;
		const addValue = (): boolean => {
			const v = input.value.trim();
			input.value = '';
			if (v) {
				const max = this.maxTags;
				if (max === null || this.value.length < max) {
					this[DATA].pop?.close();
					this.value.push(v);
					this.setSelectOnly(this.readOnly);
					this.addTag(v);
					this.emitChange();
				}
			}
			return !!v;
		};
		let arrowUpKeyDown = false;
		let arrowDownKeyDown = false;
		return (
			// biome-ignore lint/a11y/useKeyWithClickEvents: <explanation>
			<div
				class={cls(this.props.class, tagStyle)}
				style={this.props.style}
				onMouseDown={() => {
					setTimeout(() => {
						input.focus();
					});
				}}
				onClick={() => {
					if (this.selectOnly) this.openSelect();
				}}
			>
				<input
					name="tag"
					ref={(ipt) => (input = ipt)}
					onBlur={() => {
						arrowUpKeyDown = false;
						arrowDownKeyDown = false;
						addValue();
					}}
					onKeyPress={(evt) => {
						if (this.readOnly) return;
						if (
							evt.key === 'Enter' &&
							!evt.ctrlKey &&
							!evt.altKey &&
							!evt.metaKey &&
							!evt.shiftKey
						) {
							if (!addValue()) {
								this.openSelect();
							}
						}
					}}
					onKeyUp={(evt) => {
						if (this.selectOnly) return;
						if ((this.props.options ?? []).length === 0) return;
						if (
							(evt.key.length === 1 && this.input.value) ||
							(evt.key === 'ArrowDown' && arrowDownKeyDown) ||
							(evt.key === 'ArrowUp' && arrowUpKeyDown) ||
							evt.key === 'Backspace'
						) {
							if (evt.key === 'Backspace' && this.input.value.length === 0) {
								this[DATA].pop?.close();
							} else {
								const text = this.input.value;
								if (openTimer) clearTimeout(openTimer);
								openTimer = setTimeout(() => {
									this.openSelect(text);
								}, 32);
							}
						}
					}}
					onDblClick={() => {
						if (this.readOnly) return;
						this.openSelect();
					}}
					onKeyDown={(e) => {
						if (e.key === 'Backspace') {
							if (this.selectOnly) return;
							if (!input.value) {
								if (this.value.length > 0) {
									this.value.pop();
									this.setSelectOnly(this.readOnly);
									const div = input.previousElementSibling;
									if (div) {
										div.remove();
										e.stopPropagation();
										e.preventDefault();
										this.emitChange();
									}
								}
							}
						} else if (e.key === 'ArrowDown' || e.key === 'ArrowUp') {
							if (e.key === 'ArrowUp') arrowUpKeyDown = true;
							if (e.key === 'ArrowDown') arrowDownKeyDown = true;
							if (this[DATA].pop) {
								input.value = '';
								this[DATA].pop.list.focus();
								if (e.key === 'ArrowDown') {
									this[DATA].pop.list.setActive(0);
								} else {
									this[DATA].pop.list.setActive(this[DATA].pop.list.rows.length - 1);
								}
								this.input.value = this[DATA].pop.list.activeText ?? '';
								e.preventDefault();
							}
						} else if (e.key === 'ArrowLeft') {
							if (this.input.selectionStart !== 0) return;
							if (this.value.length > 0) {
								this.el
									.querySelectorAll<HTMLDivElement>(':scope>div')
									.item(this.value.length - 1)
									?.focus();
							}
						} else if (e.key === 'Escape') {
							if (this[DATA].pop) {
								this[DATA].pop.close();
							}
						}
					}}
				/>
			</div>
		);
	}
	private get input(): HTMLInputElement {
		return this.el.querySelector(':scope>input')!;
	}

	private makeTag(text: string): HTMLDivElement {
		const removeItem = () => {
			if (this.readOnly) return;
			const idx = Array.from(this.el.querySelectorAll<HTMLDivElement>(':scope>div')).indexOf(div);
			div.remove();
			this.props.value?.splice(idx, 1);
			this.setSelectOnly(this.readOnly);
			setTimeout(() => {
				this.focus();
			});
			this.emitChange();
		};
		const div = (
			<div
				tabIndex={-1}
				onPointerDown={function (evt) {
					evt.preventDefault();
					evt.stopPropagation();
					this.focus();
				}}
				onKeyDown={(evt) => {
					if ((evt.ctrlKey || evt.metaKey) && evt.key === 'c') {
						navigator.clipboard.writeText(text);
					} else if (evt.key === 'ArrowLeft') {
						evt.preventDefault();
						const idx = Array.from(this.el.querySelectorAll<HTMLDivElement>(':scope>div')).indexOf(
							div,
						);
						if (idx > 0) {
							this.el
								.querySelectorAll<HTMLDivElement>(':scope>div')
								.item(idx - 1)
								?.focus();
						}
					} else if (evt.key === 'ArrowRight') {
						evt.preventDefault();
						const idx = Array.from(this.el.querySelectorAll<HTMLDivElement>(':scope>div')).indexOf(
							div,
						);
						if (idx < this.el.querySelectorAll<HTMLDivElement>(':scope>div').length - 1) {
							this.el
								.querySelectorAll<HTMLDivElement>(':scope>div')
								.item(idx + 1)
								?.focus();
						} else {
							this.input.focus();
						}
					} else if (evt.key === 'Backspace' || evt.key === 'Delete') {
						evt.preventDefault();
						removeItem();
					}
				}}
			>
				<span>{text}</span>
				<Icon
					svg={SVGClose}
					onPointerDown={(evt) => {
						evt.preventDefault();
						evt.stopPropagation();
					}}
					onClick={(evt) => {
						evt.preventDefault();
						evt.stopPropagation();
						removeItem();
					}}
				/>
			</div>
		) as HTMLDivElement;
		return div;
	}

	private addTag(text: string) {
		this.el.insertBefore(this.makeTag(text), this.input);
	}

	clear(): this {
		this.setValue([]);
		return this;
	}

	get disabled(): boolean {
		return this.props.disabled ?? false;
	}
	setDisabled(value: boolean): this {
		this.props.disabled = value;
		this.el.classList.toggle('disabled', value);
		this.input.disabled = value;
		this.updatePlaceHolder();
		return this;
	}

	get readOnly(): boolean {
		return this.props.readOnly ?? false;
	}
	setReadOnly(value: boolean): this {
		this.props.readOnly = value;
		this.input.readOnly = value;
		this.el.classList.toggle('read-only', value);
		this.updatePlaceHolder();
		return this;
	}

	get maxTags(): number | null {
		return this.props.maxTags === undefined || isNaN(this.props.maxTags) || this.props.maxTags < 0
			? null
			: this.props.maxTags;
	}
	setMaxTags(len: number | null): this {
		this.props.maxTags = len ?? undefined;
		this.updatePlaceHolder();
		return this;
	}

	setPrefixIcon(icon: string | null): this {
		const svg = this.el.querySelector<SVGElement>(':scope>svg.prefix');
		svg?.remove();
		if (icon) {
			this.el.insertBefore(
				<Icon
					svg={icon}
					class="prefix"
					onClick={() => {
						if (this.disabled) return;
						this.props.onClickPrefixIcon?.call(this);
					}}
				/>,
				this.el.firstChild,
			);
		}
		return this;
	}

	get text(): string {
		return this.value
			.map((i) =>
				typeof i === 'string' ? i : typeof i === 'object' ? (i.text ?? i.value + '') : i + '',
			)
			.join(', ');
	}

	get textArray(): string[] {
		return this.value.map((i) =>
			typeof i === 'string' ? i : typeof i === 'object' ? (i.text ?? i.value + '') : i + '',
		);
	}

	get valueArray(): unknown[] {
		return this.value.map((i) => (typeof i === 'object' ? i.value : i));
	}

	get value(): TagValue[] {
		if (this.props.value === undefined) {
			this.props.value = [];
		}
		return this.props.value;
	}
	setValue(values: TagValue[]): this {
		this.props.value = values;
		const divs = this.el.querySelectorAll<HTMLDivElement>(':scope>div');
		divs.forEach((d) => d.remove());
		values.map((v) => {
			let s: string;
			if (typeof v === 'string') {
				s = v;
			} else if (typeof v === 'object') {
				s = v.text ?? v.value + '';
			} else {
				s = v + '';
			}
			this.addTag(s);
		});
		this.input.textContent = '';
		this.setSelectOnly(this.readOnly);
		return this;
	}

	focus(options?: FocusOptions | undefined): this {
		if (this.selectOnly) {
			this.el.focus(options);
		} else {
			this.input.focus(options);
		}
		return this;
	}

	get placeholder(): string {
		return this.props.placeholder ?? '';
	}
	setPlaceholder(text: string): this {
		this.props.placeholder = text || undefined;
		if (
			!this.readOnly &&
			!this.disabled &&
			(this.maxTags === null || this.value.length < this.maxTags) &&
			this.value.length === 0
		) {
			this.input.placeholder = text;
		} else {
			this.input.placeholder = '';
		}
		return this;
	}

	private updatePlaceHolder() {
		this.setPlaceholder(this.props.placeholder ?? '');
	}

	get maxLength(): number | null {
		return this.props.maxLength ?? null;
	}
	setMaxLength(len: number | null): this {
		this.props.maxLength = len ?? undefined;
		if (this.input) {
			if (len === null || len < 0) {
				this.input.removeAttribute('maxlength');
			} else {
				this.input.maxLength = len;
			}
		}
		return this;
	}

	get options(): TagValue[] {
		if (this.props.options === undefined) {
			this.props.options = [];
		}
		return this.props.options;
	}
	setOptions(options: TagValue[]): this {
		this.props.options = options;
		return this;
	}

	get selectOnly(): boolean {
		return this.props.selectOnly ?? false;
	}
	setSelectOnly(value: boolean): this {
		this.props.selectOnly = value;
		this.el.classList.toggle('select-only', value);
		const readOnly = value || (this.maxTags !== null && this.value.length >= this.maxTags);
		if (readOnly) {
			this.input.textContent = '';
		}
		this.input.readOnly = readOnly;
		this.updatePlaceHolder();
		return this;
	}

	onOpenSelection(handler: TagPopupHandler | undefined) {
		this.props.onOpenSelection = handler;
		return this;
	}

	onClickPrefixIcon(handler: ((this: Tag) => void) | undefined) {
		this.props.onClickPrefixIcon = handler;
		return this;
	}

	onChange(handler: ((this: Tag) => void) | undefined) {
		this.props.onChange = handler;
		return this;
	}

	onPopupOpen(handler: ((this: Tag) => void) | undefined) {
		this.props.onPopupOpen = handler;
		return this;
	}

	onPopupClose(handler: ((this: Tag) => void) | undefined) {
		this.props.onPopupClose = handler;
		return this;
	}

	private emitChange() {
		this.el.dispatchEvent(new Event('change', { bubbles: true }));
		this.props.onChange?.call(this);
	}

	private openSelect(searchText: string = '') {
		const max = this.maxTags;
		if (this.disabled || this.readOnly || (max != null && this.value.length >= max)) return;
		const values = this.value;
		if (typeof this.props.onOpenSelection === 'function') {
			this.props.onPopupOpen?.call(this);
			this.props.onOpenSelection
				?.call(this, values)
				.then((result) => {
					if (result) {
						this.setValue(result);
						this.emitChange();
					}
					setTimeout(() => {
						this.focus();
					});
				})
				.catch((reason) => {
					console.error('Tag.onOpenSelection error:', reason);
				})
				.finally(() => this.props.onPopupClose?.call(this));
			return;
		}
		const options = (this.props.options ?? [])
			.filter((o) => !values.find((v) => getValue(v) === getValue(o)))
			.filter((o) => {
				return getText(o).includes(searchText + '');
			});
		if (options.length === 0) {
			this[DATA].pop?.close();
			return;
		}
		if (this[DATA].pop) {
			this[DATA].pop.list.setRows(options);
			return;
		}
		this.props.onPopupOpen?.call(this);
		this[DATA].pop = PopList.show(options, {
			columnOption: {
				text: 'text',
				value: 'value',
			},
			refer: this.el,
			focusOnClose: this.input,
			activeValue: null,
			multiSelect: false,
			useAction: true,
			useSearch: false,
			focus: false,
		})
			.onSelect((value, text) => {
				this.input.value = '';
				this.props.onPopupClose?.call(this);
				this.setValue([...values, { value, text: text + '' }]);
				setTimeout(() => {
					this.focus();
				});
				this.emitChange();
			})
			.onClose(() => {
				this.props.onPopupClose?.call(this);
				this[DATA].pop = undefined;
			})
			.onActiveRow((row) => {
				// eslint-disable-next-line @typescript-eslint/no-explicit-any
				this.input.value = (row as any).text;
			});
	}
}
